﻿using HtmlAgilityPack;
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Data;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Messages;
using Nop.Core.Domain.Stores;
using Nop.Data;
using Nop.Plugin.Misc.MessageDesigner.Domain;
using Nop.Plugin.Misc.MessageDesigner.Models;
using Nop.Services.Events;
using Nop.Services.Localization;
using Nop.Services.Messages;
using Nop.Services.Stores;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;

namespace Nop.Plugin.Misc.MessageDesigner.Services
{
    public partial class MessageDesignerService: IMessageDesignerService
    {
        #region Constants

        /// <summary>
        /// Key for caching
        /// </summary>
        /// <remarks>
        /// {0} : store ID
        /// </remarks>
        private const string MESSAGEDESIGNS_ALL_KEY = "Nop.messagedesign.all-{0}";
        /// <summary>
        /// Key for caching
        /// </summary>
        /// <remarks>
        /// {0} : design name
        /// {1} : store ID
        /// </remarks>
        private const string MESSAGEDESIGNS_BY_NAME_KEY = "Nop.messagedesign.name-{0}-{1}";
        /// <summary>
        /// Key pattern to clear cache
        /// </summary>
        private const string MESSAGEDESIGNS_PATTERN_KEY = "Nop.messagedesign.";

        #endregion

        #region Fields

        private readonly IRepository<MessageDesign> _messageDesignRepository;
        private readonly IRepository<StoreMapping> _storeMappingRepository;
        private readonly ILanguageService _languageService;
        private readonly IStoreMappingService _storeMappingService;
        private readonly ILocalizedEntityService _localizedEntityService;
        private readonly CatalogSettings _catalogSettings;
        private readonly IEventPublisher _eventPublisher;
        private readonly ICacheManager _cacheManager;
        private readonly IMessageTemplateService _messageTemplateService;
        private readonly IRepository<MessageTemplate> _messageTemplateRepository;
        private readonly IDbContext _dbContext;
        private readonly IWebHelper _webHelper;

        #endregion

        #region Ctor

        /// <summary>
        /// Ctor
        /// </summary>
        /// <param name="cacheManager">Cache manager</param>
        /// <param name="storeMappingRepository">Store mapping repository</param>
        /// <param name="languageService">Language service</param>
        /// <param name="localizedEntityService">Localized entity service</param>
        /// <param name="storeMappingService">Store mapping service</param>
        /// <param name="messageDesignRepository">Message design repository</param>
        /// <param name="catalogSettings">Catalog settings</param>
        /// <param name="eventPublisher">Event published</param>
        public MessageDesignerService(ICacheManager cacheManager,
            IRepository<StoreMapping> storeMappingRepository,
            ILanguageService languageService,
            ILocalizedEntityService localizedEntityService,
            IStoreMappingService storeMappingService,
            IRepository<MessageDesign> messageDesignRepository,
            CatalogSettings catalogSettings,
            IEventPublisher eventPublisher,
            IMessageTemplateService messageTemplateService,
            IRepository<MessageTemplate> messageTemplateRepository,
            IDbContext dbContext, IWebHelper webHelper)
        {
            this._cacheManager = cacheManager;
            this._storeMappingRepository = storeMappingRepository;
            this._languageService = languageService;
            this._localizedEntityService = localizedEntityService;
            this._storeMappingService = storeMappingService;
            this._messageDesignRepository = messageDesignRepository;
            this._catalogSettings = catalogSettings;
            this._eventPublisher = eventPublisher;
            this._messageTemplateService = messageTemplateService;
            this._messageTemplateRepository = messageTemplateRepository;
            this._dbContext = dbContext;
            this._webHelper = webHelper;
        }

        #endregion

        #region Methods

        /// <summary>
        /// Delete a message design
        /// </summary>
        /// <param name="messageDesign">Message design</param>
        public virtual void DeleteMessageDesign(MessageDesign messageDesign)
        {
            if (messageDesign == null)
                throw new ArgumentNullException("messageDesign");

            _messageDesignRepository.Delete(messageDesign);

            _cacheManager.RemoveByPattern(MESSAGEDESIGNS_PATTERN_KEY);

            //event notification
            _eventPublisher.EntityDeleted(messageDesign);
        }

        /// <summary>
        /// Inserts a message design
        /// </summary>
        /// <param name="messageDesign">Message design</param>
        public virtual void InsertMessageDesign(MessageDesign messageDesign)
        {
            if (messageDesign == null)
                throw new ArgumentNullException("messageDesign");

            _messageDesignRepository.Insert(messageDesign);

            _cacheManager.RemoveByPattern(MESSAGEDESIGNS_PATTERN_KEY);

            //event notification
            _eventPublisher.EntityInserted(messageDesign);
        }

        /// <summary>
        /// Updates a message design
        /// </summary>
        /// <param name="messageDesign">Message design</param>
        public virtual void UpdateMessageDesign(MessageDesign messageDesign)
        {
            if (messageDesign == null)
                throw new ArgumentNullException("messageDesign");

            _messageDesignRepository.Update(messageDesign);

            _cacheManager.RemoveByPattern(MESSAGEDESIGNS_PATTERN_KEY);

            //event notification
            _eventPublisher.EntityUpdated(messageDesign);
        }

        /// <summary>
        /// Gets a message design
        /// </summary>
        /// <param name="messageDesignId">Message design identifier</param>
        /// <returns>Message design</returns>
        public virtual MessageDesign GetMessageDesignById(int messageDesignId)
        {
            if (messageDesignId == 0)
                return null;

            return _messageDesignRepository.GetById(messageDesignId);
        }

        /// <summary>
        /// Gets a message design
        /// </summary>
        /// <param name="messageDesignName">Message design name</param>
        /// <param name="storeId">Store identifier</param>
        /// <returns>Message design</returns>
        public virtual MessageDesign GetMessageDesignByName(string messageDesignName, int storeId)
        {
            if (string.IsNullOrWhiteSpace(messageDesignName))
                throw new ArgumentException("messageDesignName");

            string key = string.Format(MESSAGEDESIGNS_BY_NAME_KEY, messageDesignName, storeId);
            return _cacheManager.Get(key, () =>
            {
                var query = _messageDesignRepository.Table;
                query = query.Where(t => t.Name == messageDesignName);
                query = query.OrderBy(t => t.Id);
                query = query.OrderBy(t => t.Id);
                var designs = query.ToList();

                //store mapping
                if (storeId > 0)
                {
                    designs = designs
                        .Where(t => _storeMappingService.Authorize(t, storeId))
                        .ToList();
                }

                return designs.FirstOrDefault();
            });

        }

        /// <summary>
        /// Gets all message designs
        /// </summary>
        /// <param name="storeId">Store identifier; pass 0 to load all records</param>
        /// <returns>Message design list</returns>
        public virtual IList<MessageDesign> GetAllMessageDesigns(int storeId)
        {
            string key = string.Format(MESSAGEDESIGNS_ALL_KEY, storeId);
            return _cacheManager.Get(key, () =>
            {
                var query = _messageDesignRepository.Table;
                query = query.OrderBy(t => t.Name);

                //Store mapping
                if (storeId > 0 && !_catalogSettings.IgnoreStoreLimitations)
                {
                    query = from t in query
                            join sm in _storeMappingRepository.Table
                            on new { c1 = t.Id, c2 = "MessageDesign" } equals new { c1 = sm.EntityId, c2 = sm.EntityName } into t_sm
                            from sm in t_sm.DefaultIfEmpty()
                            where !t.LimitedToStores || storeId == sm.StoreId
                            select t;

                    //only distinct items (group by ID)
                    query = from t in query
                            group t by t.Id
                            into tGroup
                            orderby tGroup.Key
                            select tGroup.FirstOrDefault();
                    query = query.OrderBy(t => t.Name);
                }

                return query.ToList();
            });
        }

        /// <summary>
        /// merge of message design with template
        /// </summary>
        /// <param name="messageDesign">Message design</param>
        /// <returns>Message design copy</returns>
        public virtual ResultsModel MergeMessageDesign(MessageDesign messageDesign, MessageTemplate messageTemplate)
        {
            var resultModel = new ResultsModel();

            if (messageDesign == null)
                throw new ArgumentNullException("messageDesign");
            if (messageTemplate == null)
                throw new ArgumentNullException("messageTemplate");
            if (string.IsNullOrEmpty(messageTemplate.Body) || string.IsNullOrEmpty(messageDesign.Body))
            {
                resultModel.Errors.Add("Standard: no design or template found.");
                return resultModel;
            }
            //standard
            resultModel = MergeDesginTemplate(resultModel, "Standard", messageDesign.Body, messageTemplate.Body);
            if(!string.IsNullOrEmpty(resultModel.MergedBody))
            {
                messageTemplate.Body = resultModel.MergedBody;
                _messageTemplateService.UpdateMessageTemplate(messageTemplate);
                resultModel.Success.Add("Standard: design added to template.");
            }
            //localization
            var languages = _languageService.GetAllLanguages(true);
            foreach (var lang in languages)
            {
                var bodyDesign = messageDesign.GetLocalized(x => x.Body, lang.Id, false, false);
                var bodyTemplate = messageTemplate.GetLocalized(x => x.Body, lang.Id, false, false);
                if (string.IsNullOrEmpty(bodyDesign) || string.IsNullOrEmpty(bodyTemplate))
                {
                    resultModel.Errors.Add(lang.Name + ": no design or template found.");
                    continue;
                }
                resultModel.MergedBody = null;
                resultModel = MergeDesginTemplate(resultModel, lang.Name, bodyDesign, bodyTemplate);
                if (!string.IsNullOrEmpty(resultModel.MergedBody))
                {
                    _localizedEntityService.SaveLocalizedValue(messageTemplate, x => x.Body, resultModel.MergedBody, lang.Id);             
                    resultModel.Success.Add(lang.Name + ": design added to template.");
                }
            }
            return resultModel;
        }

        /// <summary>
        /// remove of message design from template
        /// </summary>
        /// <returns>Message design copy</returns>
        public virtual void RemoveAllDesigns()
        {
            var messageTemplates = _messageTemplateService.GetAllMessageTemplates(0);
            foreach (var mt in messageTemplates)
            {
                var resultsModel = RemoveMessageDesign(mt);
            }
        }

        /// <summary>
        /// remove of message design from template
        /// </summary>
        /// <param name="messageDesign">Message design</param>
        /// <returns>Message design copy</returns>
        public virtual ResultsModel RemoveMessageDesign(MessageTemplate messageTemplate)
        {
            if (messageTemplate == null)
                throw new ArgumentNullException("messageTemplate");
            var resultModel = new ResultsModel();   
            //standard
            resultModel = RemoveDesginTemplate(resultModel, "Standard", messageTemplate.Body);
            if (!string.IsNullOrEmpty(resultModel.OriginalBody))
            {
                messageTemplate.Body = resultModel.OriginalBody;
                _messageTemplateService.UpdateMessageTemplate(messageTemplate);
                resultModel.Success.Add("Standard: design has been removed from template.");
            }

            var languages = _languageService.GetAllLanguages(true);
            foreach (var lang in languages)
            {
                var bodyTemplate = messageTemplate.GetLocalized(x => x.Body, lang.Id, false, false);
                if (string.IsNullOrEmpty(bodyTemplate))
                {
                    resultModel.Errors.Add(lang.Name + ": no design or template found.");
                    continue;
                }
                resultModel.OriginalBody = null;
                resultModel = RemoveDesginTemplate(resultModel, lang.Name, bodyTemplate);
                if (!string.IsNullOrEmpty(resultModel.OriginalBody))
                {
                    _localizedEntityService.SaveLocalizedValue(messageTemplate, x => x.Body, resultModel.OriginalBody, lang.Id);   
                    resultModel.Success.Add(lang.Name + ": Design has been removed from template.");
                }
            }

            return resultModel;
        }

        private ResultsModel RemoveDesginTemplate(ResultsModel resultModel, string langName, string bodyTemplate)
        {
            var designResultModel = IsMessageDesignerContent(bodyTemplate);
            if (designResultModel.IsSuccess)
            {
                resultModel.OriginalBody = designResultModel.InnerText;
            }
            else
            {
                resultModel.Errors.Add(designResultModel.Error);
                resultModel.Errors.Add(langName + ": Design does not exist ( Could not find 'messageDesignerId' in template)");
            }
            return resultModel;
        }

        public ResultsModel MergeDesginTemplate(ResultsModel resultModel, string langName, string bodyDesign, string bodyTemplate)
        {
            var designResultModel = IsMessageDesignerContent(bodyDesign);
            var templateResultModel = IsMessageDesignerContent(bodyTemplate);
            if (designResultModel.IsSuccess)
            {
                if (!templateResultModel.IsSuccess)
                {
                    try
                    {
                        var doc = new HtmlDocument();
                        doc.LoadHtml(bodyDesign);
                        doc.GetElementbyId("messageDesignerId").InnerHtml = @bodyTemplate;
                        resultModel.MergedBody = doc.DocumentNode.OuterHtml;
                    }
                    catch (Exception exc)
                    {
                        resultModel.Errors.Add(langName + ": " + exc.Message);
                    }
                }
                else
                {
                    if (templateResultModel.IdValue == designResultModel.IdValue)
                        resultModel.Errors.Add(langName + ": Design already exist in template");
                    else
                    {
                        bodyTemplate =  RemoveDesginTemplate(resultModel, langName, bodyTemplate).OriginalBody;
                        try
                        {
                            var doc = new HtmlDocument();
                            doc.LoadHtml(bodyDesign);
                            doc.GetElementbyId("messageDesignerId").InnerHtml = @bodyTemplate;
                            resultModel.MergedBody = doc.DocumentNode.OuterHtml;
                        }
                        catch (Exception exc)
                        {
                            resultModel.Errors.Add(langName + ": " + exc.Message);
                        }
                    }
                }
            }
            else
            {
                resultModel.Errors.Add(designResultModel.Error);
                resultModel.Errors.Add(langName + ": Could not find 'messageDesignerId' in design");
            }
            return resultModel;
        }

        public DesignResultsModel IsMessageDesignerContent(string body)
        {
            var model = new DesignResultsModel()
            {
                IsSuccess = false,
            };
           
            try
            {
                var doc = new HtmlDocument();
                doc.LoadHtml(body);
                HtmlNode htmlDiv = doc.GetElementbyId("messageDesignerId");
                model.InnerText = htmlDiv.InnerHtml;
                HtmlNode htmlDivName = doc.GetElementbyId("messageDesignerName");
                int valueId;
                model.IdValue = 0;
                if (htmlDivName !=null)
                { 
                    if (Int32.TryParse(htmlDivName.Attributes["data-id"].Value, out valueId))
                        model.IdValue = valueId; 
                }  
                model.IsSuccess = true;
                return model;
            }
            catch (Exception exc)
            {
                model.Error = exc.Message;
                return model;
            }
        }
        public DesignResultsModel IsMessageDesignerId(string body)
        {
            var model = new DesignResultsModel()
            {
                IsSuccess = false,
            };

            try
            {
                var doc = new HtmlDocument();
                doc.LoadHtml(body);
                HtmlNode htmlDivName = doc.GetElementbyId("messageDesignerName");
                int valueId;
                if (Int32.TryParse(htmlDivName.Attributes["data-id"].Value, out valueId))
                    model.IdValue = valueId;
                else
                    model.IdValue = 0;
                model.IsSuccess = true;
                return model;
            }
            catch (Exception exc)
            {
                model.Error = exc.Message;
                return model;
            }
        }

        #endregion

        #region Message Template
        /// <summary>
        /// Get message templates by identifiers
        /// </summary>
        /// <param name="messageTemapleIds">messageTemplate identifiers</param>
        /// <returns>MessageTemplates</returns>
        public virtual IList<MessageTemplate> GetMessageTemplatesByIds(int[] messageTemplatIds)
        {
            if (messageTemplatIds == null || messageTemplatIds.Length == 0)
                return new List<MessageTemplate>();

            var query = from mt in _messageTemplateRepository.Table
                        where messageTemplatIds.Contains(mt.Id)
                        select mt;
            var messageTemplates = query.ToList();
            //sort by passed identifiers
            var sortedMessageTemplates = new List<MessageTemplate>();
            foreach (int id in messageTemplatIds)
            {
                var messageTemplate = messageTemplates.Find(x => x.Id == id);
                if (messageTemplate != null)
                    sortedMessageTemplates.Add(messageTemplate);
            }
            return sortedMessageTemplates;
        }
        #endregion

        #region SQL
        public virtual void ExecuteSQL(string fileName)
        {
            ExecuteSqlFile(_webHelper.MapPath("~/Plugins/Misc.MessageDesigner/Data/" + fileName));
        }
        protected virtual void ExecuteSqlFile(string path)
        {
            var statements = new List<string>();

            using (var stream = File.OpenRead(path))
            using (var reader = new StreamReader(stream))
            {
                string statement;
                while ((statement = ReadNextStatementFromStream(reader)) != null)
                    statements.Add(statement);
            }

            foreach (string stmt in statements)
                _dbContext.ExecuteSqlCommand(stmt);
        }
        protected virtual string ReadNextStatementFromStream(StreamReader reader)
        {
            var sb = new StringBuilder();
            while (true)
            {
                var lineOfText = reader.ReadLine();
                if (lineOfText == null)
                {
                    if (sb.Length > 0)
                        return sb.ToString();

                    return null;
                }

                if (lineOfText.TrimEnd().ToUpper() == "GO")
                    break;

                sb.Append(lineOfText + Environment.NewLine);
            }

            return sb.ToString();
        }
        #endregion
    }
}
